<?php
namespace app\common\service;




class WechatPayV2
{
    private $appid;         // 公众号/小程序ID
    private $mchid;         // 商户号
    private $key;           // 商户密钥
    private $notify_url;    // 回调地址

    public function __construct($config)
    {
        $this->appid = $config['appid'];
        $this->mchid = $config['mchid'];
        $this->key = $config['apiv3_key'];
        $this->notify_url = $config['notify_url'];
    }

    /**
     * JSAPI/小程序下单
     * @param array $params 订单参数
     * @return array 支付所需参数
     */
    public function createOrder($params)
    {
        // 统一下单参数
        $data = [
            'appid' => $this->appid,
            'mch_id' => $this->mchid,
            'nonce_str' => $this->generateNonceStr(),
            'body' => $params['body'],
            'out_trade_no' => $params['out_trade_no'],
            'total_fee' => intval($params['total_fee'] * 100), // 单位：分
            'spbill_create_ip' => $_SERVER['REMOTE_ADDR'],
            'notify_url' => $this->notify_url,
            'trade_type' => 'JSAPI',
            'openid' => $params['openid']  // 用户openid
        ];

        // 生成签名并加入请求数据
        $data['sign'] = $this->generateSign($data);

        // 请求统一下单接口
        $xml = $this->arrayToXml($data);
        $response = $this->postXmlCurl($xml, 'https://api.mch.weixin.qq.com/pay/unifiedorder');
        $result = $this->xmlToArray($response);

        if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
            // 生成JSAPI支付参数
            $jsApiParams = [
                'appId' => $this->appid,
                'timeStamp' => (string)time(),
                'nonceStr' => $this->generateNonceStr(),
                'package' => 'prepay_id=' . $result['prepay_id'],
                'signType' => 'MD5'
            ];
            $jsApiParams['paySign'] = $this->generateSign($jsApiParams);

            return $jsApiParams;
        } else {
            return [
                'code' => 0,
                'msg' => isset($result['err_code_des']) ? $result['err_code_des'] : $result['return_msg']
            ];
        }
    }

    /**
     * 生成随机字符串
     */
    private function generateNonceStr($length = 32)
    {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
        $str = '';
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }

    /**
     * 生成签名
     */
    private function generateSign($data)
    {
        // 移除空值和sign字段
        $data = array_filter($data);
        if (isset($data['sign'])) {
            unset($data['sign']);
        }

        // 按照键名升序排序
        ksort($data);

        // 拼接键值对
        $stringA = '';
        foreach ($data as $k => $v) {
            $stringA .= "{$k}={$v}&";
        }
        $stringA .= "key={$this->key}";

        // MD5签名并转为大写
        return strtoupper(md5($stringA));
    }

    /**
     * 数组转XML
     */
    private function arrayToXml($data)
    {
        $xml = '<xml>';
        foreach ($data as $key => $val) {
            $xml .= '<' . $key . '>' . $val . '</' . $key . '>';
        }
        $xml .= '</xml>';
        return $xml;
    }

    /**
     * XML转数组
     */
    /**
     * 将XML字符串转换为数组
     * @param string $xml XML字符串
     * @return array 转换后的数组
     */
    private function xmlToArray($xml)
    {
        // 使用 LIBXML_NOENT | LIBXML_NOCDATA 标志来禁用外部实体加载
        // 同时处理CDATA部分
        $options = LIBXML_NOCDATA | LIBXML_NONET;

        // 保存旧的libxml错误处理设置
        $oldUseInternalErrors = libxml_use_internal_errors(true);

        // 解析XML
        $simpleXml = simplexml_load_string($xml, 'SimpleXMLElement', $options);

        // 处理错误
        if ($simpleXml === false) {
            $errors = libxml_get_errors();
            libxml_clear_errors();
            libxml_use_internal_errors($oldUseInternalErrors);
            throw new \Exception('XML解析失败：' . $errors[0]->message);
        }

        // 恢复旧的错误处理设置
        libxml_use_internal_errors($oldUseInternalErrors);

        // 转换为数组
        return json_decode(json_encode($simpleXml), true);
    }

    /**
     * 发送POST请求
     */
    private function postXmlCurl($xml, $url, $timeout = 30)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_HEADER, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);

        $response = curl_exec($ch);
        curl_close($ch);

        return $response;
    }

    /**
     * 验证回调签名
     */
    public function verifyNotify($xml)
    {
        $data = $this->xmlToArray($xml);
        $sign = $data['sign'];
        $calculatedSign = $this->generateSign($data);

        return $sign == $calculatedSign;
    }
}